import React, {Component} from 'react'
import {MarkdownPreview} from 'react-marked-markdown';
// import py1_1 from '../static/py1_1.md'
// import marked from 'marked';

class Py23 extends Component {
    constructor(props) {
        super(props);
        this.state = {
           value: `

当你真正迈出网络数据采集基础之门的时候，遇到的第一个问题很可能是：“我怎么获取
登录窗口背后的信息呢？”今天，网络正在朝着页面交互、社交媒体、用户产生内容的趋
势不断地演进。表单和登录窗口是许多网站中不可或缺的组成部分。不过，它们还是比较
容易处理的。

到目前为止，我们示例中的网络爬虫在和大多数网站的服务器进行数据交互时，都是用
HTTP 协议的 GET 方法去请求信息。这一章，我们将重点介绍 POST 方法，即把信息推送给
网络服务器进行存储和分析。

页面表单基本上可以看成是一种用户提交 POST 请求的方式，且这种请求方式是服务器能够
理解和使用的。就像网站的 URL 链接可以帮助用户发送 GET 请求一样，HTML 表单可以
帮助用户发出 POST 请求。当然，我们也可以用一点儿代码来自己创建这些请求，然后通过
网络爬虫把它们提交给服务器。

> 9.1　Python Requests库

虽然用 Python 的标准库也可以控制网页表单，但是有时用一点儿语法糖可以让生活更甜
蜜。当你想做比 urllib 库能够实现的基本 GET 请求更多的事情时，可以看看 Python 标准
库之外的第三方库。

Requests 库（http://www.python-requests.org/）就是这样一个擅长处理那些复杂的 HTTP 请
求、cookie、header（响应头和请求头）等内容的 Python 第三方库。

下面是 Requests 的创建者 Kenneth Reitz 对 Python 标准库工具的评价。

    Python 的标准库 urllib2 为你提供了大多数 HTTP 功能，但是它的 API 非常差劲。这是
    因为它是经过许多年一步步建立起来的——不同时期要面对的是不同的网络环境。于是
    为了完成最简单的任务，它需要耗费大量的工作（甚至要重写整个方法）。

    事情不应该这样复杂，更不应该发生在 Python 里。

和任何 Python 第三方库一样，Requests 库也可以用其他第三方 Python 库管理器安装，比
如 pip，或者直接下载源代码（https://github.com/kennethreitz/requests/tarball/master）安装。

> 9.2　提交一个基本表单

大多数网页表单都是由一些 HTML 字段、一个提交按钮、一个在表单处理完之后跳转的
“执行结果”（表单属性 action 的值）页面构成。虽然这些 HTML 字段通常由文字内容构
成，但是也可以实现文件上传或其他非文字内容。

因为大多数主流网站都会在它们的 robots.txt 文件里注明禁止爬虫接入登录表单（附录 C
介绍了采集这类表单的相关法律责任），所以为了安全起见，我在网站里放入了一组不
同类型的表单和登录内容，这样你就可以用网络爬虫采集了。最简单的表单位于 http://pythonscraping.com/pages/files/form.html。

这个表单的源代码是：
    
    <form method="post" action="processing.php">
    First name: <input type="text" name="firstname"><br>
    Last name: <input type="text" name="lastname"><br>
    <input type="submit" value="Submit">
    </form>
  
这里有几点需要注意一下：首先，两个输入字段的名称是 firstname 和 lastname ，这一点非
常重要。字段的名称决定了表单被确认后要被传送到服务器上的变量名称。如果你想模拟
表单提交数据的行为，你就需要保证你的变量名称与字段名称是一一对应的。

还需要注意表单的真实行为其实发生在 processing.php（绝对路径是 http://pythonscraping.com/files/processing.php）。表单的任何 POST 请求其实都发生在这个页面上，并非表单本身
所在的页面。切记：HTML 表单的目的，只是帮助网站的访问者发送格式合理的请求，向
服务器请求没有出现的页面。除非你要对请求的设计样式进行研究，否则不需要花太多时
间在表单所在的页面上。

用 Requests 库提交表单只用四行代码就可以实现，包括导入库文件和打印内容的语句（是
的，就是这么简单）：
   
    import requests

    params = {'firstname': 'Ryan', 'lastname': 'Mitchell'}
    r = requests.post("http://pythonscraping.com/files/processing.php", data=params)
    print(r.text)
   
表单被提交之后，程序应该会返回执行页面的源代码，包括这行内容：
     
    Hello there, Ryan Mitchell!
    
这个程序还可以用来处理许多网站的简单表单。比如 O’Reilly Media 新闻订阅页面的表单
源代码如下所示：
   
    <form action="http://post.oreilly.com/client/o/oreilly/forms/quicksignup.cgi" id="example_form2" method="POST">
        <input name="client_token" type="hidden" value="oreilly" />
        <input name="subscribe" type="hidden" value="optin" />
        <input name="success_url" type="hidden" value="http://oreilly.com/store/newsletter-thankyou.html" />
        <input name="error_url" type="hidden" value="http://oreilly.com/store/newsletter-signup-error.html" />
        <input name="topic_or_dod" type="hidden" value="1" />
        <input name="source" type="hidden" value="orm-home-t1-dotd" />
        <fieldset>
            <input class="email_address long" maxlength="200" name="email_addr" size="25" type="text" value="Enter your email here" />
            <button alt="Join" class="skinny" name="submit" onclick="return addClickTracking('orm','ebook','rightrail','dod');" value="submit">Join</button>
        </fieldset>
    </form>
   
虽然第一次看这些会觉得恐怖，但是大多数情况下（后面我们会介绍异常）你只需要关注
两件事：

* 你想提交数据的字段名称（在这个例子中是 email_addr ）
* 表单的 action 属性，也就是表单提交后网站会显示的页面（在这个例子中是 http://post.oreilly.com/client/o/oreilly/forms/quicksignup.cgi）

把对应的信息增加到请求信息中，运行代码即可：
     
    import requests
    params = {'email_addr': 'ryan.e.mitchell@gmail.com'}
    r = requests.post("http://post.oreilly.com/client/o/oreilly/forms/
    quicksignup.cgi", data=params)
    print(r.text)
   
在这个示例中，你真正加入 O’Reilly 的邮件列表之前，还要在网站上填写另一个表单，同
样的代码也可以应用到需要填写的新表单上。不过，如果你自己在家做，希望你慎用这些
知识，不要给 O’Reilly 出版社提交无效的注册。

> 9.3　单选按钮、复选框和其他输入

显然，并非所有的网页表单都只是一堆文本字段和一个提交按钮。HTML 标准里提供了大
量可用的表单字段：单选按钮、复选框和下拉选框等。在 HTML5 里面，还有其他的控件，
像滚动条（范围输入字段）、邮箱、日期等。自定义的 JavaScript 字段可谓无所不能，可以
实现取色器（colorpicker）、日历以及开发者能想到的任何功能。

无论表单的字段看起来多么复杂，仍然只有两件事是需要关注的：字段名称和值。字段名
称可以通过查看源代码寻找 name 属性轻易获得。而字段的值有时会比较复杂，有可能是在
表单提交之前通过 JavaScript 生成的。取色器就是一个比较奇怪的表单字段，它可能会用
类似 #F03030 这样的值。

如果你不确定一个输入字段值的数据格式，有一些工具可以跟踪浏览器正在通过网站发出
或接受的 GET 和 POST 请求的内容。之前提到过，跟踪 GET 请求效果最好也最直接的手段就
是看网站的 URL 链接。如果 URL 链接像这样：
    
    http://domainname.com?thing1=foo&thing2=bar
     
你明白这个请求就是下面这种表单：
   
    <form method="GET" action="someProcessor.php">
    <input type="someCrazyInputType" name="thing1" value="foo" />
    <input type="anotherCrazyInputType" name="thing2" value="bar" />
    <input type="submit" value="Submit" />
    </form>
    
对应的 Python 参数就是：
    
    {'thing1':'foo', 'thing2':'bar'}

如果你遇到了一个看着比较复杂的 POST 表单，并且想查看浏览器向服务器传递了哪些参
数，最简单的方法就是用 Chrome 浏览器的审查元素（inspector）或开发者工具查看。

Chrome 浏览器的开发者工具可以在菜单中通过更多工具→开发者工具打开（快捷键 F12）。
它提供了浏览器与网站交互时产生的所有请求细节，是一种查看请求参数的好方法。

> 9.4　提交文件和图像

虽然上传文件在网络上很普遍，但是对于网络数据采集其实不太常用。但是，如果你想为
自己网站的文件上传功能写一个测试实例，也是可以实现的。不管怎么说，掌握工作原理
总是有用的。

在 http://pythonscraping/files/form2.html 有一个文件上传表单，页面上表单的源代码如下
所示：
    
    <form action="processing2.php" method="post" enctype="multipart/form-data">
    Submit a jpg, png, or gif: <input type="file" name="image"><br>
    <input type="submit" value="Upload File">
    </form>
   
文件上传表单除了 \<input> 标签里有一个 type 属性是 file ，看起来和之前看到的文字字段
的表单没什么两样。其实，Python Requests 库对这种表单的处理方式也和之前的非常相似：
   
    import requests

    files = {'uploadFile': open('../files/Python-logo.png', 'rb')}
    r = requests.post("http://pythonscraping.com/pages/processing2.php",files=files)
    print(r.text)
    
需要注意，这里提交给表单字段 uploadFile 的值不是一个简单的字符串了，而是一个用
open 函数打开的 Python 文件对象。在这个例子中，我提交了一个保存在我电脑上的图像
文件，文件路径是相对这个 Python 程序所在位置的 ../files/Python-logo.png。

没错，就是这么简单！

> 9.5　处理登录和cookie

到此为止，我们介绍过的大多数表单都允许你向网站提交信息，或者让你在提交表单后立
即看到想要的页面信息。那么，这些表单和登录表单（当你浏览网站时让你保持“已登
录”状态）有什么不同？

大多数新式的网站都用 cookie 跟踪用户是否已登录的状态信息。一旦网站验证了你的登录
权证，它就会将它们保存在你的浏览器的 cookie 中，里面通常包含一个服务器生成的令
牌、登录有效时限和状态跟踪信息。网站会把这个 cookie 当作信息验证的证据，在你浏览
网站的每个页面时出示给服务器。在 20 世纪 90 年代中期广泛使用 cookie 之前，保证用户
安全验证并跟踪用户是网站上的一大问题。

虽然 cookie 为网络开发者解决了大问题，但同时却为网络爬虫带来了大问题。你可以一整
天只提交一次登录表单，但是如果你没有一直关注表单后来回传给你的那个 cookie，那么
一段时间以后再次访问新页面时，你的登录状态就会丢失，需要重新登录。

我在 http://pythonscraping.com/pages/cookies/login.html 建了一个简单的登录表单（用户名可
以是任意值，但是密码必须是“password”）。

这个表单在欢迎页面（http://pythonscraping.com/pages/cookies/welcome.php）处理，里面包
含一个简介页面：http://pythonscraping.com/pages/cookies/profile.php。

如果在登录网站之前你想进入欢迎页面或者简介页面，会看到一个错误信息和访问前请先
登录的指令。在简介页面中，网站会检测浏览器的 cookie，看它有没有页面已登录的设置
信息。

用 Requests 库跟踪 cookie 同样很简单：
   
    import requests

    params = {'username': 'Ryan', 'password': 'password'}
    r = requests.post("http://pythonscraping.com/pages/cookies/welcome.php", params)
    print("Cookie is set to:")
    print(r.cookies.get_dict())
    print("-----------")
    print("Going to profile page...")
    r = requests.get("http://pythonscraping.com/pages/cookies/profile.php",cookies=r.cookies)
    print(r.text)
    
这里我向欢迎页面发送了一个登录参数，它的作用就像登录表单的处理器。然后我从请求
结果中获取 cookie，打印登录状态的验证结果，然后再通过 cookies 参数把 cookie 发送到
简介页面。

对简单的访问这样处理没有问题，但是如果你面对的网站比较复杂，它经常暗自调整
cookie，或者如果你从一开始就完全不想要用 cookie，该怎么处理呢？ Requests 库的
session 函数可以完美地解决这些问题：
    
    import requests

    session = requests.Session()

    params = {'username': 'username', 'password': 'password'}
    s = session.post("http://pythonscraping.com/pages/cookies/welcome.php", params)
    print("Cookie is set to:")
    print(s.cookies.get_dict())
    print("-----------")
    print("Going to profile page...")
    s = session.get("http://pythonscraping.com/pages/cookies/profile.php")
    print(s.text)
  
在这个例子中，会话（session）对象（调用 requests.Session() 获取）会持续跟踪会话信
息，像 cookie、header，甚至包括运行 HTTP 协议的信息，比如 HTTPAdapter（为 HTTP
和 HTTPS 的链接会话提供统一接口）。

Requests 是一个非常给力的库，程序员完全不用费脑子，也不用写代码，可能只逊色于
Selenium（第 10 章将会介绍）。虽然写网络爬虫的时候，你可能想放手让 Requests 库替自
己做所有的事情，但是持续关注 cookie 的状态，掌握它们可以控制的范围是非常重要的。
这样可以避免痛苦地调试和追寻网站行为异常，节省很多时间。

#### HTTP基本接入认证

在发明 cookie 之前，处理网站登录最常用的方法就是用 HTTP 基本接入认证（HTTP basic
access authentication）。有时还能见到它们，尤其是在一些安全性较高的网站或公司网站，
以及一些 API 的使用上。我在 http://pythonscraping.com/pages/auth/login.php 用这种认证方
法创建了一个页面

和前面的例子一样，你可以用任意用户名，但是密码必须是“password”。

Requests 库有一个 auth 模块专门用来处理 HTTP 认证：
     
    import requests
    from requests.auth import AuthBase
    from requests.auth import HTTPBasicAuth

    auth = HTTPBasicAuth('ryan', 'password')
    r = requests.post(url="http://pythonscraping.com/pages/auth/login.php", auth=auth)
    print(r.text)
     
虽然这看着像是一个普通的 POST 请求，但是有一个 HTTPBasicAuth 对象作为 auth 参数传
递到请求中。显示的结果将是用户名和密码验证成功的页面（如果验证失败，就是一个拒
绝接入页面）。

> 9.6　其他表单问题

表单是网络恶意机器人（malicious bots）酷爱的网站切入点。你当然不希望机器人创建垃
圾账号，占用昂贵的服务器资源，或者在博客上提交垃圾评论。因此，新式的网站经常在
HTML 中使用很多安全措施，让表单不能被快速穿越。

关于验证码（CAPTCHA）的作用，请查看第 11 章内容，里面介绍了 Python 的图像处理
和文本识别方法。

如果你在提交表单的时候遇到了一个莫名其妙的错误，或者服务器一直以陌生的理由拒绝
你，请查看第 12 章内容，里面介绍了蜜罐（honey pot）、隐含字段（hidden field），以及其
他保护网页表单的安全措施。
    
           
           `,
        };
      }
    render() {
        return (
            <div >
                {/* 111111111111111
                <Py112 /> */}
                <MarkdownPreview value={this.state.value}/>
            </div>
        )
    }
}

export default Py23